package com.hollycrm.sip.proxy;

import com.hollycrm.rtp.RtpProxy;
import com.hollycrm.sip.proxy.config.SPConfig;
import com.hollycrm.util.log.IceLogger;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;



public class SipProxy implements Runnable {
    static DatagramSocket socket = null;
    public static final int MAX_DATAGRAM_SIZE = 8 * 1024;
    public static final String CHARTSET = "UTF-8";
    public static final String FROM = "From";
    public static final String TO = "To";
    public static final String VIA = "Via";
    public static final String CONTACT = "Contact";
    public static final String REGISTER = "REGISTER";

    private static Logger log = Logger.getLogger(SipProxy.class.getName());

    private Hashtable<String, String> peers = new Hashtable<String, String>();
    private List<String> peerExtens = new ArrayList<String>();

    private Hashtable<String, RtpProxy> calls = new Hashtable<String, RtpProxy>();

    private boolean updatting = false;

    public static void main(String[] a) {
        SPConfig.getInstance();
        IceLogger.initLogger(SPConfig.getInstance().logDir, "sipPorxy",
                SPConfig.getInstance().logLevel,
                SPConfig.getInstance().logConsole);
        SipProxy proxy = new SipProxy();

        try {
            InetSocketAddress add = new InetSocketAddress(SPConfig
                    .getInstance().ip, SPConfig.getInstance().port);
            socket = new DatagramSocket(add);
            socket.setReceiveBufferSize(MAX_DATAGRAM_SIZE);
        } catch (SocketException e) {
            log.log(Level.WARNING, "", e);
        }
        Thread t = new Thread(proxy);
        t.start();
    }

    public void run() {
        if (socket == null) {
            return;
        }

        byte[] receiveData = new byte[MAX_DATAGRAM_SIZE];
        DatagramPacket packet = new DatagramPacket(receiveData,
                receiveData.length);

        String bufferExten = null;
        while (true && socket != null) {
            try {
                packet.setLength(MAX_DATAGRAM_SIZE);
                socket.receive(packet);

                int packetLength = packet.getLength();
                // Read bytes and put it in a eueue.
                byte[] bytes = packet.getData();
                byte[] msgBytes = new byte[packetLength];
                System.arraycopy(bytes, 0, msgBytes, 0, packetLength);
                bytes = null;

                String fromIp = packet.getAddress().getHostAddress();
                int fromPort = packet.getPort();
                // message from asterisk, encrypt it and send to client
                if (fromIp.equals(SPConfig.getInstance().astIp)) {
                    String rc4Message = new String(msgBytes, "UTF-8");
                    log.info("From asterisk: " + fromIp+":"+fromPort
                            + "-----------------\r\n" + rc4Message + "\r\n");
                    SipMessage sip = new SipMessage();
                    sip.paser(rc4Message);
                    if (SPConfig.getInstance().encryptRTP) {
                        if (sip.getRequest().startsWith("INVITE")) {
                            addCall(sip);
                        } else if (sip.getRequest()
                                .startsWith("SIP/2.0 200 OK")) {
                            updateCall(sip);
                        } else if (sip.getRequest().startsWith("SIP/2.0 4")) {
                            endCall(sip);
                        } else if (sip.getRequest().startsWith("SIP/2.0 5")) {
                            endCall(sip);
                        } else if (sip.getRequest().startsWith("BYE")
                                || sip.getRequest().startsWith("CANCEL")) {
                            endCall(sip);
                        }
                    }

                    // SIP/2.0/UDP 10.8.103.42:5060;received=218.241.181.25
                    To to = new To(sip.getHeader(TO));
                    String exten = to.getExten();
                    if (!peers.containsKey(exten)) {
                        From from = new From(sip.getHeader(FROM));
                        exten = from.getExten();
                        if (!peers.containsKey(exten)) {
                            rc4Message = null;
                            msgBytes = null;
                            continue;
                        }
                    }
                    if (sip.getHeader(VIA) != null) {
                        Via via = new Via(sip.getHeader(VIA));
                        via.setPort(SPConfig.getInstance().port);
                        sip.addHeader(VIA, via.getString());
                    }
                    if (sip.getHeader(CONTACT) != null) {
                        Contact contact = new Contact(sip.getHeader(CONTACT));
                        contact.setPort(SPConfig.getInstance().port);
                        sip.addHeader(CONTACT, contact.getString());
                    }
                    String[] a = peers.get(exten).split(":");
                    String ip = a[0];
                    int port = Integer.valueOf(a[1]);
                    InetSocketAddress add = new InetSocketAddress(ip, port);
                    DatagramPacket dp = new DatagramPacket(new byte[0], 0, add);
                    String strdata = sip.getSipMessage();
                    byte[] d = strdata.getBytes("UTF-8");
                    RC4 rc4 = new RC4(SPConfig.getInstance().encryptKey);
                    byte[] data = rc4.rc4(d);
                    dp.setData(data);
                    dp.setLength(data.length);
                    log.info("Send to [" + ip + ":" + port + "]: \r\n"
                            + strdata);
                    socket.send(dp);
                    continue;
                }
                // message from client, dencrypt it and send to asterisk
                else {
                    String rc4Message = new String(msgBytes, CHARTSET);
                    String sipMessage = rc4Message;
                    if (rc4Message.length() == 4) {
                        sipMessage = null;
                        rc4Message = null;
                        msgBytes = null;
                        continue;
                    } else {
                        RC4 rc4 = new RC4(SPConfig.getInstance().encryptKey);
                        byte[] data = rc4.rc4(msgBytes);
                        sipMessage = new String(data, CHARTSET);
                        log.info("From client: " + fromIp+":"+fromPort
                                        + "-----------------\r\n" + sipMessage
                                        + "\r\n");
                    }
                    SipMessage sip = new SipMessage();
                    sip.paser(sipMessage);
                    if (SPConfig.getInstance().encryptRTP) {
                        if (sip.getRequest().startsWith("INVITE")) {
                            addCall(sip);
                        } else if (sip.getRequest()
                                .startsWith("SIP/2.0 200 OK")) {
                            updateCall(sip);
                        } else if (sip.getRequest().startsWith("SIP/2.0 4")) {
                            endCall(sip);
                        } else if (sip.getRequest().startsWith("SIP/2.0 5")) {
                            endCall(sip);
                        } else if (sip.getRequest().startsWith("BYE")
                                || sip.getRequest().startsWith("CANCEL")) {
                            endCall(sip);
                        }
                    }
                    if (sip.getHeader(FROM) == null) {
                        log.warning("Get unvariable sip message whihc is without [From] header: "
                                + fromIp + ":" + fromPort + "\n" + sipMessage);
                        continue;
                    }
                    From from = new From(sip.getHeader(FROM));
                    String exten = from.getExten();
                    if (sip.getRequest().startsWith(REGISTER)) {
                        bufferExten = new String(exten.toCharArray());
                        if (!peerExtens.contains(bufferExten)) {
                            peerExtens.add(bufferExten);
                        }
                        peers.put(bufferExten, fromIp + ":" + fromPort);
                    }
                    InetSocketAddress address = new InetSocketAddress(SPConfig
                            .getInstance().astIp,
                            SPConfig.getInstance().astPort);
                    DatagramPacket dp = new DatagramPacket(new byte[0], 0,
                            address);
                    String strdata = sip.getSipMessage();
                    byte[] d = strdata.getBytes(CHARTSET);
                    dp.setData(d);
                    dp.setLength(d.length);
                    log.info("Send to [" + SPConfig.getInstance().astIp + ":"
                            + SPConfig.getInstance().astPort + "]: \r\n"
                            + strdata);
                    socket.send(dp);
                    sipMessage = null;
                    rc4Message = null;
                    msgBytes = null;
                }
            } catch (Exception e) {
                log.log(Level.WARNING, "", e);
            } finally {

            }
        }
    }

    // INVITE
    private void addCall(SipMessage message) {
        String callID = message.getHeader("Call-ID");
        if (callID == null)
            return;
        String userAgent = message.getHeader("User-Agent");

        RtpProxy call = new RtpProxy(callID);
        call.start();
        // INVITE from asterisk
        if (userAgent != null && userAgent.equals("Asterisk PBX")) {
            call.setAstIp(message.getSdp().getConnection_address());
            call.setAstPort(message.getSdp().getPort());
        }
        // INVITE from client
        else {
            call.setAgentIp(message.getSdp().getConnection_address());
            call.setAgentPort(message.getSdp().getPort());
        }
        message.getSdp().setConnection_address(SPConfig.getInstance().ip);
        message.getSdp().setPort(call.getListenPort());
        calls.put(callID, call);
    }

    // 200 ok
    private void updateCall(SipMessage message) {
        String callID = message.getHeader("Call-ID");
        if (callID == null)
            return;
        String userAgent = message.getHeader("User-Agent");
        RtpProxy call = calls.get(callID);
        if (call == null) {
            return;
        }
        // INVITE from asterisk
        if (userAgent != null && userAgent.equals("Asterisk PBX")) {
            call.setAstIp(message.getSdp().getConnection_address());
            call.setAstPort(message.getSdp().getPort());
        }
        // INVITE from client
        else {
            call.setAgentIp(message.getSdp().getConnection_address());
            call.setAgentPort(message.getSdp().getPort());
        }
        message.getSdp().setConnection_address(SPConfig.getInstance().ip);
        message.getSdp().setPort(call.getListenPort());
    }

    private void endCall(SipMessage message) {
        String callID = message.getHeader("Call-ID");
        if (callID == null)
            return;
        RtpProxy call = calls.remove(callID);
        if (call != null) {
            call.stop();
        }
    }

    private void updateLocation() {
        if (updatting)
            return;
        updatting = true;
        Thread t = new Thread() {
            public void run() {
                while (true) {
                    try {
                        File file = new File("location");
                        if (!file.exists()) {
                            file.createNewFile();
                        }
                        StringBuffer sb = new StringBuffer();
                        for (int i = 0; i < peerExtens.size(); i++) {
                            sb.append(peerExtens.get(i) + "="
                                    + peers.get(peerExtens.get(i)) + "\r\n");
                        }
                        FileWriter fw = null;
                        try {
                            fw = new FileWriter(file);
                            fw.write(sb.toString());
                        } catch (Exception e) {
                            log.log(Level.WARNING, "", e);
                        } finally {
                            if (fw != null)
                                try {
                                    fw.close();
                                } catch (IOException e) {
                                    log.log(Level.WARNING, "", e);
                                }
                        }
                    } catch (Exception e) {
                        log.log(Level.WARNING, "", e);
                    }
                }
            }
        };
        t.start();
    }

    private void loadLocation() {
        try {

        } catch (Exception e) {

        }
    }
}
